package edu.pku.sei.autounit.reflect.jdt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;

import edu.pku.sei.autounit.annotation.Context;
import edu.pku.sei.autounit.annotation.AutoUnitMethod;
import edu.pku.sei.autounit.reflect.AbstractContextType;
import edu.pku.sei.autounit.reflect.Class;
import edu.pku.sei.autounit.reflect.Constructor;
import edu.pku.sei.autounit.reflect.Interface;
import edu.pku.sei.autounit.reflect.Method;
import edu.pku.sei.autounit.reflect.Type;
import edu.pku.sei.autounit.reflect.TypeVariable;
import edu.pku.sei.autounit.reflect.java.JavaTypeParameter;
import edu.pku.sei.autounit.reflect.java.JavaTypeVariable;
import edu.pku.sei.autounit.util.Log;

public class JDTClass extends AbstractContextType implements Class{
	
	private IType jdtType;
	private JDTTypeFactory factory;
	private String simpleName;
	private String fullName;
	private String packageName;
	private Class supperClass;
	private Interface [] interfaces;
	private int mod;
	private Constructor[] constructors;
	
	protected JDTClass(IType jdtType,JDTTypeFactory factory){
		this.packageName = jdtType.getPackageFragment().getElementName();
		this.jdtType = jdtType;
		this.factory = factory;
		this.fullName = jdtType.getFullyQualifiedName().replace('$','.');
		this.simpleName = jdtType.getTypeQualifiedName().replace('$','.');
		
		try {
			this.mod = jdtType.getFlags();
			ITypeParameter []itp = jdtType.getTypeParameters();
			typeParameters = new JavaTypeParameter[itp.length];
			String[] contexts = getAnnotationContext();
			
			for(int i=0;i<itp.length;i++){
				typeParameters[i] = new JavaTypeParameter(itp[i].getElementName());
				if(i<contexts.length)
					typeParameters[i].setContext(factory.getType(contexts[i],jdtType,this));
				
				String []bs = itp[i].getBounds();
				Type []b = new Type[bs.length];
				for(int j=0;j<bs.length;j++){
					b[j] = factory.getType(bs[j], jdtType,this);
				}
				typeParameters[i].setBounds(b);
				
			}
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "JDTClass", "", e);
		}
	}
	
	protected JDTClass(IType jdtType,Type []contexts,JDTTypeFactory factory){
		this.packageName = jdtType.getPackageFragment().getElementName();
		this.jdtType = jdtType;
		this.factory = factory;
		this.fullName = jdtType.getFullyQualifiedName();
		this.simpleName = jdtType.getTypeQualifiedName();

		try {
			this.mod = jdtType.getFlags();
			ITypeParameter []itp = jdtType.getTypeParameters();
			typeParameters = new JavaTypeParameter[itp.length];
			
			for(int i=0;i<itp.length;i++){
				typeParameters[i] = new JavaTypeParameter(itp[i].getElementName());
				typeParameters[i].setContext(contexts[i]);
				
				String []bs = itp[i].getBounds();
				Type []b = new Type[bs.length];
				for(int j=0;j<bs.length;j++){
					b[j] = factory.getType(bs[j], jdtType,this);
				}
				typeParameters[i].setBounds(b);
				
			}
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "JDTClass", "", e);
		}
	}
	
	private String[] getAnnotationContext(){
		IAnnotation ia = jdtType.getAnnotation(Context.class.getName());
		if(!ia.exists())
			ia = jdtType.getAnnotation(Context.class.getSimpleName());
		if(!ia.exists())
			return new String[0];
		try {
			IMemberValuePair[] mvs = ia.getMemberValuePairs();
			if(mvs.length != 0){
				Object obj = ia.getMemberValuePairs()[0].getValue();
				String[] r = new String[java.lang.reflect.Array.getLength(obj)];
				for(int i =0;i<r.length;i++){
					r[i] = (String)java.lang.reflect.Array.get(obj,i);
				}
				return r;
			}
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "getAnnotationContext", "", e);
		}
		return new String[0];
	}
	
	
	public String getName(Map<String, String> map){
		StringBuffer sb = new StringBuffer();
		if(fullName.startsWith("java.lang"))
			sb.append(simpleName);
		else{
			String f = map.get(simpleName);
			if(f == null){
				map.put(simpleName, fullName);
				sb.append(simpleName);
			}
			else if(f.equals(fullName))
				sb.append(simpleName);
			else
				sb.append(fullName);
		}

		Type [] contexts = getContexts();
		if(contexts.length == 0)
			return sb.toString();
		sb.append("<");
		
		for(int i=0;i<contexts.length-1;i++)
			sb.append(contexts[i].getName(map)+",");
		sb.append(contexts[contexts.length-1].getName(map)+">");
		return sb.toString();
	}

	
	public Constructor[] getPublicConstructors() {
		if (constructors == null) {
			try {
				boolean hasConstructor = false;
				List<Constructor> list = new ArrayList<Constructor>();
				for (IMethod im : jdtType.getMethods()) {
					if (!im.isConstructor())
						continue;
					hasConstructor = true;
					if (!Flags.isPublic(im.getFlags()))
						continue;
					list.add(new JDTConstructor(jdtType, this, im, factory));
				}
				if (!hasConstructor) {
					list.add(new DefaultConstructor(this));
				}
				constructors = list.toArray(new Constructor[list.size()]);
			} catch (JavaModelException e) {
				Log.gotUnexpectedExection("JDTClass",
						"getAllPublicConstructor", "", e);
			}
		}
		return constructors;
	}

	
	public Method[] getTestMethods() {
		try {
			List<Method>methods = new ArrayList<Method>();
			for(IMethod im: jdtType.getMethods()){
				if(!Flags.isPublic(im.getFlags()))
					continue;
				//if(!im.getAnnotation(JUGMethod.class.getSimpleName()).exists()&&
				//		!im.getAnnotation(JUGMethod.class.getName()).exists())
				//	continue;
				if(im.isConstructor())
					continue;
				methods.add(new JDTMethod(jdtType, this, im, factory));
			}
			return methods.toArray(new Method[methods.size()]);
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "getTestMethods", "", e);
		}
		return new Method[0];
	}
	
	
	public Method[] getPublicMethods() {
		try {
			List<Method>methods = new ArrayList<Method>();
			for(IMethod im: jdtType.getMethods()){
				if(!Flags.isPublic(im.getFlags()))
					continue;
				if(im.getAnnotation(AutoUnitMethod.class.getSimpleName())==null&&
						im.getAnnotation(AutoUnitMethod.class.getName())==null)
					continue;
				if(im.isConstructor())
					continue;
				methods.add(new JDTMethod(jdtType, this, im, factory));
			}
			return methods.toArray(new Method[methods.size()]);
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "getPublicMethods", "", e);
		}
		return new Method[0];
	}

	
	public String getFullName() {
		return fullName;
	}

	
	public String getPackageName() {
		return packageName;
	}

	
	public Iterator<Class> getSubClasses() {

		try {
			ITypeHierarchy th = jdtType.newTypeHierarchy(new NullProgressMonitor());
			final IType[] its = th.getSubclasses(jdtType);
			
			Iterator<Class> r = new Iterator<Class>(){
				private int index = 0;
				private Class next;
				
				public boolean hasNext() {
					for(;index<its.length;index++){
						Class sub = getSubClass(its[index]);
						if(sub!=null){
							next = sub;
							return true;
						}
					}
					return false;
				}

				
				public Class next() {
					index++;
					return next;
				}

				
				public void remove() {
					index = 0;
				}
				
			};
			return r;
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "getSubClasses", "", e);
		}
		return null;
	}
	
	private Class getSubClass(IType t){
		
		try {
			if (t.isInterface())
				return null;
			String tn = t.getSuperclassName();
			Class c = (Class) factory.getType(tn, t, null);
			Type[] cs = c.getContexts();
			Type[] thisContexts = getContexts();
			Map<String, Type> map = new HashMap<String, Type>();
			boolean macth = true;
			if (cs.length == thisContexts.length) {
				for (int i = 0; i < cs.length; i++) {
					if (cs[i] instanceof TypeVariable)
						map.put(cs[i].getFullName(), thisContexts[i]);
					else if (!cs[i].equals(thisContexts[i])) {
						macth = false;
						break;
					}
				}
				if (macth) {
					ITypeParameter[] itp = t.getTypeParameters();
					Type[] ts = new Type[itp.length];
					for (int i = 0; i < ts.length; i++) {
						String name = itp[i].getElementName();
						Type mt = map.get(name);
						if (mt != null)
							ts[i] = mt;
						else
							ts[i] = new JavaTypeVariable(name, new Type[0]);
					}
					return (Class) factory.getType(t, ts);
				}
			}
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "getSubClasses", "", e);
		}
		return null;
	}
	
	
	public Class getSuperClass() {
		if(supperClass == null){
			try {
			String supperClassName = jdtType.getSuperclassName();
			if(supperClassName != null)
				supperClass = (Class)factory.getType(supperClassName, jdtType,this);
			else{
				if(fullName.equals(Object.class.getName()))
					supperClass = null;
				else
					supperClass = (Class)factory.getType(Object.class.getName(), jdtType,this);
			}
			} catch (JavaModelException e) {
				Log.gotUnexpectedExection("JDTClass", "getSuperClass", "", e);
			}
		}
		return supperClass;
	}

	
	public Interface[] getInterfaces() {
		if(interfaces == null){
			try {
				String[] ifs = jdtType.getSuperInterfaceNames();
				interfaces = new Interface[ifs.length];
				for(int i = 0;i<ifs.length;i++)
					interfaces[i] = (Interface)factory.getType(ifs[i], jdtType,this);
			} catch (JavaModelException e) {
				Log.gotUnexpectedExection("JDTClass", "getInterfaces", "", e);
			}
		}
		return interfaces;
	}

	
	public boolean canInstantiation() {
		if(!Flags.isAbstract(mod)&&Flags.isPublic(mod)&&
				this.getPublicConstructors().length!=0)
			return true;
		else
			return false;
	}
}
